/*-------------------------------------------------------------------------
 *
 * printtup.c
 *	  向目标打印元组的例程（支持前端
 *	  客户端和独立后端）。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/access/common/printtup.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/printtup.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "tcop/pquery.h"
#include "utils/lsyscache.h"
#include "utils/memdebug.h"
#include "utils/memutils.h"


static void fc_printtup_startup(DestReceiver *fc_self, int fc_operation,
							 TupleDesc fc_typeinfo);
static bool fc_printtup(TupleTableSlot *fc_slot, DestReceiver *fc_self);
static void fc_printtup_shutdown(DestReceiver *fc_self);
static void fc_printtup_destroy(DestReceiver *fc_self);

/* ----------------------------------------------------------------
 *		printtup / debugtup 支持
 * ----------------------------------------------------------------
 */

/* ----------------
 *		printtup 目标对象的私有状态
 *
 * 注意：finfo是用于typoutput或typsend的查找信息，具体使用哪一个
 * 取决于我们对于这一列的使用。
 * ----------------
 */
typedef struct
{								/* 每个属性的信息 */
	Oid			typoutput;		/* 类型的文本输出函数的 Oid */
	Oid			typsend;		/* 类型的二进制输出函数的 Oid */
	bool		typisvarlena;	/* 它是否为变长类型（即可能可压缩）？ */
	int16		format;			/* 此列的格式代码 */
	FmgrInfo	finfo;			/* 输出函数的预计算调用信息 */
} PrinttupAttrInfo;

typedef struct
{
	DestReceiver pub;			/* 公共已知的函数指针 */
	Portal		portal;			/* 我们正在打印的门户 */
	bool		sendDescrip;	/* 启动时发送 RowDescription？ */
	TupleDesc	attrinfo;		/* 我们已设置的属性信息 */
	int			nattrs;
	PrinttupAttrInfo *myinfo;	/* 关于每个属性的缓存信息 */
	StringInfoData buf;			/* 输出缓冲区（*不* 在 tmpcontext 中） */
	MemoryContext tmpcontext;	/* 每行工作区的内存上下文 */
} DR_printtup;

/* ----------------
 *		初始化：为 printtup 创建一个 DestReceiver
 * ----------------
 */
DestReceiver * printtup_create_DR(CommandDest fc_dest)
{
	DR_printtup *fc_self = (DR_printtup *) palloc0(sizeof(DR_printtup));

	fc_self->pub.receiveSlot = fc_printtup;	/* 可能会在稍后更改 */
	fc_self->pub.rStartup = fc_printtup_startup;
	fc_self->pub.rShutdown = fc_printtup_shutdown;
	fc_self->pub.rDestroy = fc_printtup_destroy;
	fc_self->pub.mydest = fc_dest;

	/*
	 * 如果 DestRemote，则自动发送 T 消息，但如果是
	 * DestRemoteExecute 则不发送
	 */
	fc_self->sendDescrip = (fc_dest == DestRemote);

	fc_self->attrinfo = NULL;
	fc_self->nattrs = 0;
	fc_self->myinfo = NULL;
	fc_self->buf.data = NULL;
	fc_self->tmpcontext = NULL;

	return (DestReceiver *) fc_self;
}

/*
 * 为 DestRemote（或 DestRemoteExecute）接收器设置参数
 */
void SetRemoteDestReceiverParams(DestReceiver *fc_self, Portal fc_portal)
{
	DR_printtup *fc_myState = (DR_printtup *) fc_self;

	Assert(fc_myState->pub.mydest == DestRemote ||
		   fc_myState->pub.mydest == DestRemoteExecute);

	fc_myState->portal = fc_portal;
}

static void fc_printtup_startup(DestReceiver *fc_self, int fc_operation, TupleDesc fc_typeinfo)
{
	DR_printtup *fc_myState = (DR_printtup *) fc_self;
	Portal		fc_portal = fc_myState->portal;

	/*
	 * 创建一个用于所有消息的 I/O 缓冲区。 这不能在
	 * tmpcontext 内，因为我们希望在行之间重用它。
	 */
	initStringInfo(&fc_myState->buf);

	/*
	 * 创建一个临时内存上下文，我们可以在每行重置一次以
	 * 回收内存分配的内存。 这避免了数据类型输出例程内部的
	 * 内存泄漏问题，而且应该比逐个释放内存更快。
	 */
	fc_myState->tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
												"printtup",
												ALLOCSET_DEFAULT_SIZES);

	/*
	 * 如果我们需要发出行描述，则发送元组的
	 * 描述符。
	 */
	if (fc_myState->sendDescrip)
		SendRowDescriptionMessage(&fc_myState->buf,
								  fc_typeinfo,
								  FetchPortalTargetList(fc_portal),
								  fc_portal->formats);

	/* ----------------
	 * 我们可以在此时设置派生属性信息，但我们将其推迟到
	 * printtup 的第一次调用，原因有二：
	 * 1. 如果根本没有元组输出，相较于旧方法，我们不会浪费时间。
	 * 2. 在 printtup 中检查允许我们处理中途改变类型的元组
	 *	  的情况（尽管在当前执行器中这可能不发生）。
	 * ----------------
	 */
}

/*
 * SendRowDescriptionMessage --- 向前端发送 RowDescription 消息
 *
 * 注意：TupleDesc 通常是通过 ExecTypeFromTL()
 * 或某些类似函数制造的；它不包含完整的字段集。
 * 当执行没有计划的实用函数时，目标列表将为 NIL。
 * 如果目标列表不是 NIL，那么它是查询节点的目标列表；
 * 我们必须忽略其中的 resjunk 列。 formats[]
 * 数组指针可能为 NULL（如果我们在执行准备语句的
 * 描述操作）；在这种情况下发送格式代码的零值。
 */
void SendRowDescriptionMessage(StringInfo fc_buf, TupleDesc fc_typeinfo,
						  List *fc_targetlist, int16 *fc_formats)
{
	int			fc_natts = fc_typeinfo->natts;
	int			fc_i;
	ListCell   *fc_tlist_item = list_head(fc_targetlist);

	/* 元组描述符消息类型 */
	pq_beginmessage_reuse(fc_buf, 'T');
	/* 元组中的属性数量 */
	pq_sendint16(fc_buf, fc_natts);

	/*
	 * 预分配要发送的整个消息的内存。这允许使用
	 * 显著更快的内联 pqformat.h 函数，并避免
	 * 重新分配内存。
	 *
	 * 必须高估列名的大小，以考虑
	 * 字符集开销。
	 */
	enlargeStringInfo(fc_buf, (NAMEDATALEN * MAX_CONVERSION_GROWTH /* attname */
							+ sizeof(Oid)	/* resorigtbl */
							+ sizeof(AttrNumber)	/* resorigcol */
							+ sizeof(Oid)	/* atttypid */
							+ sizeof(int16) /* attlen */
							+ sizeof(int32) /* attypmod */
							+ sizeof(int16) /* format */
							) * fc_natts);

	for (fc_i = 0; fc_i < fc_natts; ++fc_i)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_typeinfo, fc_i);
		Oid			fc_atttypid = fc_att->atttypid;
		int32		fc_atttypmod = fc_att->atttypmod;
		Oid			fc_resorigtbl;
		AttrNumber	fc_resorigcol;
		int16		fc_format;

		/*
		 * 如果列是一个域，发送基本类型和 typmod 代替。
		 * 在发送任何整数之前进行查找，以提高效率。
		 */
		fc_atttypid = getBaseTypeAndTypmod(fc_atttypid, &fc_atttypmod);

		/* 我们有非 resjunk 的 tlist 项吗？ */
		while (fc_tlist_item &&
			   ((TargetEntry *) lfirst(fc_tlist_item))->resjunk)
			fc_tlist_item = lnext(fc_targetlist, fc_tlist_item);
		if (fc_tlist_item)
		{
			TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_tlist_item);

			fc_resorigtbl = fc_tle->resorigtbl;
			fc_resorigcol = fc_tle->resorigcol;
			fc_tlist_item = lnext(fc_targetlist, fc_tlist_item);
		}
		else
		{
			/* 没有可用信息，因此发送零 */
			fc_resorigtbl = 0;
			fc_resorigcol = 0;
		}

		if (fc_formats)
			fc_format = fc_formats[fc_i];
		else
			fc_format = 0;

		pq_writestring(fc_buf, NameStr(fc_att->attname));
		pq_writeint32(fc_buf, fc_resorigtbl);
		pq_writeint16(fc_buf, fc_resorigcol);
		pq_writeint32(fc_buf, fc_atttypid);
		pq_writeint16(fc_buf, fc_att->attlen);
		pq_writeint32(fc_buf, fc_atttypmod);
		pq_writeint16(fc_buf, fc_format);
	}

	pq_endmessage_reuse(fc_buf);
}

/*
 * 获取 printtup() 所需的查找信息
 */
static void fc_printtup_prepare_info(DR_printtup *fc_myState, TupleDesc fc_typeinfo, int fc_numAttrs)
{
	int16	   *fc_formats = fc_myState->portal->formats;
	int			fc_i;

	/* 清除任何旧数据 */
	if (fc_myState->myinfo)
		pfree(fc_myState->myinfo);
	fc_myState->myinfo = NULL;

	fc_myState->attrinfo = fc_typeinfo;
	fc_myState->nattrs = fc_numAttrs;
	if (fc_numAttrs <= 0)
		return;

	fc_myState->myinfo = (PrinttupAttrInfo *)
		palloc0(fc_numAttrs * sizeof(PrinttupAttrInfo));

	for (fc_i = 0; fc_i < fc_numAttrs; fc_i++)
	{
		PrinttupAttrInfo *fc_thisState = fc_myState->myinfo + fc_i;
		int16		fc_format = (fc_formats ? fc_formats[fc_i] : 0);
		Form_pg_attribute fc_attr = TupleDescAttr(fc_typeinfo, fc_i);

		fc_thisState->format = fc_format;
		if (fc_format == 0)
		{
			getTypeOutputInfo(fc_attr->atttypid,
							  &fc_thisState->typoutput,
							  &fc_thisState->typisvarlena);
			fmgr_info(fc_thisState->typoutput, &fc_thisState->finfo);
		}
		else if (fc_format == 1)
		{
			getTypeBinaryOutputInfo(fc_attr->atttypid,
									&fc_thisState->typsend,
									&fc_thisState->typisvarlena);
			fmgr_info(fc_thisState->typsend, &fc_thisState->finfo);
		}
		else
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("unsupported format code: %d", fc_format)));
	}
}

/* ----------------
 *		printtup --- 将元组发送到客户端
 * ----------------
 */
static bool fc_printtup(TupleTableSlot *fc_slot, DestReceiver *fc_self)
{
	TupleDesc	fc_typeinfo = fc_slot->tts_tupleDescriptor;
	DR_printtup *fc_myState = (DR_printtup *) fc_self;
	MemoryContext fc_oldcontext;
	StringInfo	fc_buf = &fc_myState->buf;
	int			fc_natts = fc_typeinfo->natts;
	int			fc_i;

	/* 设置或更新我的派生属性信息（如有必要） */
	if (fc_myState->attrinfo != fc_typeinfo || fc_myState->nattrs != fc_natts)
		fc_printtup_prepare_info(fc_myState, fc_typeinfo, fc_natts);

	/* 确保元组已完全解构 */
	slot_getallattrs(fc_slot);

	/* 切换到逐行上下文，以便我们可以回收下面的内存 */
	fc_oldcontext = MemoryContextSwitchTo(fc_myState->tmpcontext);

	/*
	 * 准备一个 DataRow 消息（注意缓冲区在逐行上下文中）
	 */
	pq_beginmessage_reuse(fc_buf, 'D');

	pq_sendint16(fc_buf, fc_natts);

	/*
	 * 发送此元组的属性
	 */
	for (fc_i = 0; fc_i < fc_natts; ++fc_i)
	{
		PrinttupAttrInfo *fc_thisState = fc_myState->myinfo + fc_i;
		Datum		fc_attr = fc_slot->tts_values[fc_i];

		if (fc_slot->tts_isnull[fc_i])
		{
			pq_sendint32(fc_buf, -1);
			continue;
		}

		/*
		 * 在这里我们捕获在返回给客户端的
		 * 数据中未定义的字节，而不经过磁盘；有关相关检查的评论见
		 * PageAddItem()。 此测试对于未压缩的、
		 * 非外部数据最有用，但在测试新的 C 函数时
		 * 我们很有可能在这里看到这样的情况。
		 */
		if (fc_thisState->typisvarlena)
			VALGRIND_CHECK_MEM_IS_DEFINED(DatumGetPointer(attr),
										  VARSIZE_ANY(attr));

		if (fc_thisState->format == 0)
		{
			/* 文本输出 */
			char	   *fc_outputstr;

			fc_outputstr = OutputFunctionCall(&fc_thisState->finfo, fc_attr);
			pq_sendcountedtext(fc_buf, fc_outputstr, strlen(fc_outputstr), false);
		}
		else
		{
			/* 二进制输出 */
			bytea	   *fc_outputbytes;

			fc_outputbytes = SendFunctionCall(&fc_thisState->finfo, fc_attr);
			pq_sendint32(fc_buf, VARSIZE(fc_outputbytes) - VARHDRSZ);
			pq_sendbytes(fc_buf, VARDATA(fc_outputbytes),
						 VARSIZE(fc_outputbytes) - VARHDRSZ);
		}
	}

	pq_endmessage_reuse(fc_buf);

	/* 返回到调用者的上下文，并清空行的临时内存 */
	MemoryContextSwitchTo(fc_oldcontext);
	MemoryContextReset(fc_myState->tmpcontext);

	return true;
}

/* ----------------
 *		printtup_shutdown
 * ----------------
 */
static void fc_printtup_shutdown(DestReceiver *fc_self)
{
	DR_printtup *fc_myState = (DR_printtup *) fc_self;

	if (fc_myState->myinfo)
		pfree(fc_myState->myinfo);
	fc_myState->myinfo = NULL;

	fc_myState->attrinfo = NULL;

	if (fc_myState->buf.data)
		pfree(fc_myState->buf.data);
	fc_myState->buf.data = NULL;

	if (fc_myState->tmpcontext)
		MemoryContextDelete(fc_myState->tmpcontext);
	fc_myState->tmpcontext = NULL;
}

/* ----------------
 *		printtup_destroy
 * ----------------
 */
static void fc_printtup_destroy(DestReceiver *fc_self)
{
	pfree(fc_self);
}

/* ----------------
 *		printatt
 * ----------------
 */
static void fc_printatt(unsigned fc_attributeId,
		 Form_pg_attribute fc_attributeP,
		 char *fc_value)
{
#ifdef FDD
	printf("\t%2u: %s%s%s%s\t(typeid = %u, len = %d, typmod = %d, byval = %c)\n",
#else
	printf("\t%2d: %s%s%s%s\t(typeid = %u, len = %d, typmod = %d, byval = %c)\n",
#endif		  
		   fc_attributeId,
		   NameStr(fc_attributeP->attname),
		   fc_value != NULL ? " = \"" : "",
		   fc_value != NULL ? fc_value : "",
		   fc_value != NULL ? "\"" : "",
		   (unsigned int) (fc_attributeP->atttypid),
		   fc_attributeP->attlen,
		   fc_attributeP->atttypmod,
		   fc_attributeP->attbyval ? 't' : 'f');
}

/* ----------------
 *		debugStartup - 准备为交互式后端打印元组
 * ----------------
 */
void debugStartup(DestReceiver *fc_self, int fc_operation, TupleDesc fc_typeinfo)
{
	int			fc_natts = fc_typeinfo->natts;
	int			fc_i;

	/*
	 * 显示元组的返回类型
	 */
	for (fc_i = 0; fc_i < fc_natts; ++fc_i)
		fc_printatt((unsigned) fc_i + 1, TupleDescAttr(fc_typeinfo, fc_i), NULL);
	printf("\t----\n");
}

/* ----------------
 *		debugtup - 为交互式后端打印一个元组
 * ----------------
 */
bool debugtup(TupleTableSlot *fc_slot, DestReceiver *fc_self)
{
	TupleDesc	fc_typeinfo = fc_slot->tts_tupleDescriptor;
	int			fc_natts = fc_typeinfo->natts;
	int			fc_i;
	Datum		fc_attr;
	char	   *fc_value;
	bool		fc_isnull;
	Oid			fc_typoutput;
	bool		fc_typisvarlena;

	for (fc_i = 0; fc_i < fc_natts; ++fc_i)
	{
		fc_attr = slot_getattr(fc_slot, fc_i + 1, &fc_isnull);
		if (fc_isnull)
			continue;
		getTypeOutputInfo(TupleDescAttr(fc_typeinfo, fc_i)->atttypid,
						  &fc_typoutput, &fc_typisvarlena);

		fc_value = OidOutputFunctionCall(fc_typoutput, fc_attr);

		fc_printatt((unsigned) fc_i + 1, TupleDescAttr(fc_typeinfo, fc_i), fc_value);
	}
	printf("\t----\n");

	return true;
}
